package org.zhuzx.util;

import com.alibaba.fastjson.JSON;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 对apache.httpcomponents的简单封装（使用4.5版本），支持https，使用UTF-8字符集。
 * @author	zhuzx	2017年2月11日
 */
public class HttpClientUtil {
	
	private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
	
	private static final ContentType TEXT_PLAIN = ContentType.create("text/plain", Consts.UTF_8);
	
	/**
	 * @param url 可带参数
	 * @param params 其中的参数将会转移到url后面拼接，可为空。
	 */
	public static String get(String url, Map<String,String> params) {
		return get(url, params, null);
	}
	
	/**
	 * @param url 可带参数
	 * @param params 其中的参数将会转移到url后面拼接，可为空。
	 * @param headers 额外的请求头，可为空。
	 */
	public static String get(String url, Map<String,String> params, Map<String,String> headers) {
		url = WebUtil.transferParamsIntoUrl(url, params);
		url = url.replace(" ", "%20");
		HttpGet httpGet = new HttpGet(url);
		return executeRequest(httpGet, null, headers);
	}
	
	/**
	 * 以普通表单提交形式调用。<br>
	 * 此方法内部固定使用application/x-www-form-urlencoded传输，
	 * 自动将params中的键值对以a=1&b=2形式拼接后放入请求体。
	 * @param url 若url后面携带了参数则会自动转移到params中
	 * @param params 可为空
	 */
	public static String post(String url, Map<String,String> params) {
		return post(url, params, null, null);
	}

	/**
	 * 将bodyData转换成json串传输（若本身是字符串则原文传输）。<br>
	 * 此方法内部固定使用application/json;charset=UTF-8的Content-Type。
	 * @param bodyData 任意格式对象，但不能为null或者非json格式的字符串。
	 * @param headers 额外的请求头，可为空。
	 */
	public static String postJson(String url, Object bodyData, Map<String,String> headers) {
		HttpPost httpPost = new HttpPost(url);
		Assert.notNull(bodyData, "请求体不能为null");
		String json = bodyData instanceof String ? (String) bodyData : JSON.toJSONString(bodyData);
		HttpEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
		return executeRequest(httpPost, entity, headers);
	}

	/**
	 * 通用的post方法，将bodyData的数据以UTF-8编码后直接放入方法体。<br>
	 * 若未指定Content-Type，则默认使用text/plain传输。
	 * @param bodyData 数据主体，可以是任意形式，不可为空。
	 * @param headers 额外的请求头，可为空。
	 */
	public static String post(String url, String bodyData, Map<String,String> headers) {
		HttpPost httpPost = new HttpPost(url);
		HttpEntity entity = new StringEntity(bodyData, StandardCharsets.UTF_8);
		return executeRequest(httpPost, entity, headers);
	}
	
	/**
	 * 上传文件流，可附带传输其它的普通文本参数。
	 * @param url 若url后面携带了参数则会自动转移到params中
	 * @param params 可为空
	 * @param headers 额外的请求头，可为空。
	 * @param ins 需要上传的文件流，可为空。
	 * @param fileNames 需要上传的文件的名称，必须与文件流一一对应。
	 */
	public static String postInputStream(String url, Map<String,String> params, Map<String,String> headers,
			Map<String,InputStream> ins, Map<String,String> fileNames) {
		Map<String,ContentBody> bodyParts = new HashMap<>();
		if (!GeneralUtil.isEmpty(ins)) {
			for (Map.Entry<String, InputStream> entry : ins.entrySet()) {
				bodyParts.put(entry.getKey(), new ByteArrayBody(
						GeneralUtil.getBytes(entry.getValue()), ContentType.APPLICATION_OCTET_STREAM,
						fileNames.get(entry.getKey())));
			}
		}
		return post(url, params, headers, bodyParts);
	}
	
	/**
	 * bodyParts中的ContentBody必须携带有文件名
	 */
	private static String post(String url, Map<String,String> params, Map<String,String> headers,
			Map<String,ContentBody> bodyParts) {
		if (params == null) {
			params = new HashMap<>();
		}
		url = WebUtil.transferParamsIntoMap(url, params);
		
		HttpEntity entity;
		if (!GeneralUtil.isEmpty(bodyParts)) {
			MultipartEntityBuilder builder = MultipartEntityBuilder.create();
			builder.setMode(HttpMultipartMode.RFC6532);
			String ct = headers.get("Content-Type");
			if (!GeneralUtil.isBlank(ct)) {
				int index = ct.indexOf("boundary");
				if (index > -1) {
					String[] temp = ct.split("\\s*;\\s*");
					for (String item : temp) {
						if (item.startsWith("boundary")) {
							String boundary = item.split("=")[1].trim();
							builder.setBoundary(boundary);
						}
					}
				}
			}
			for (Map.Entry<String, String> entry : params.entrySet()) {
				builder.addTextBody(entry.getKey(), entry.getValue(), TEXT_PLAIN);
			}
			for (Map.Entry<String, ContentBody> entry : bodyParts.entrySet()) {
				if (GeneralUtil.isBlank(entry.getValue().getFilename())) {
					throw new IllegalArgumentException("文件实体" + entry.getKey() + "缺失文件名");
				}
				builder.addPart(entry.getKey(), entry.getValue());
			}
			entity = builder.build();
		} else {
			List<NameValuePair> formParams = new ArrayList<>();
			for (Map.Entry<String, String> entry : params.entrySet()) {
				formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
			}
			entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
		}
		
		HttpPost httpPost = new HttpPost(url);
		return executeRequest(httpPost, entity, headers);
	}
	
	//@Deprecated//设置自动重试
	private static String executeRequest(HttpRequestBase request, HttpEntity entity, Map<String,String> headers) {
		String url = request.getURI().toString();
		boolean needSsl = url.startsWith("https");
		CloseableHttpClient httpclient = needSsl ? createSSLInsecureClient() : HttpClients.createDefault();
		CloseableHttpResponse response = null;
		if (!GeneralUtil.isEmpty(headers)) {
			for (Map.Entry<String, String> entry : headers.entrySet()) {
				request.setHeader(entry.getKey(), entry.getValue());
			}
		}
		if (entity != null && request instanceof HttpEntityEnclosingRequestBase) {
			((HttpEntityEnclosingRequestBase) request).setEntity(entity);
		}
		try {
			response = httpclient.execute(request);
			HttpEntity resEntity = response.getEntity();
			String result = EntityUtils.toString(resEntity, Consts.UTF_8);
			EntityUtils.consume(resEntity);
			int code = response.getStatusLine().getStatusCode();
			if (code < 200 || code >= 300) {
				logger.warn("从目标地址{}收到了非正常的HTTP状态码：{}，响应数据：{}", url, response.getStatusLine(), result);
			}
			return result;
		} catch (IOException e) {
			String bizErrMsgPrefix = "调用远程接口时发生网络错误";
			logger.error(bizErrMsgPrefix + "，目标地址：" + url, e);
			throw new RuntimeException(bizErrMsgPrefix + "：" + e.getMessage(), e);
		} finally {
			try {
				if (response != null) {
					response.close();
				}
				httpclient.close();
			} catch (IOException e) {
				logger.error("无法关闭IO资源", e);
			}
		}
	}
	
	private static CloseableHttpClient createSSLInsecureClient() {
		try {
			SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(
					null, (TrustStrategy) (chain, authType) -> true).build();
			SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
			return HttpClients.custom().setSSLSocketFactory(sslsf).build();
		} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
			logger.error("无法初始化CloseableHttpClient", e);
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * @param bodyData 数据主体，可以是任意形式，不可为空。
	 * @param headers 额外的请求头，可为空。
	 */
	public static String put(String url, String bodyData, Map<String,String> headers) {
		return executeRequest(new HttpPut(url), new StringEntity(bodyData, StandardCharsets.UTF_8), headers);
	}

	/**
	 * @param bodyData 数据主体，可以是任意形式，不可为空。
	 * @param headers 额外的请求头，可为空。
	 */
	public static String patch(String url, String bodyData, Map<String,String> headers) {
		return executeRequest(new HttpPatch(url), new StringEntity(bodyData, StandardCharsets.UTF_8), headers);
	}

	/**
	 * @param headers 额外的请求头，可为空。
	 */
	public static String delete(String url, Map<String,String> headers) {
		return executeRequest(new HttpDelete(url), null, headers);
	}
	
}